#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Algorithm特定Side-Channel Attack基Class
ForNot同密码AlgorithmProvide专门的Attack建模
"""

import numpy as np
import logging
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, List, Tuple, Union
from dataclasses import dataclass

try:
    from ..base_side_channel_attack import BaseSideChannelAttack, AttackResult
    from ..cpa_attack import CPAAttack
    from ..dpa_attack import DPAAttack
    from ..spa_attack import SPAAttack
    from ..template_attack import TemplateAttack
except ImportError:
    # If相对导入Failed，Use绝对Path
    import sys
    import os
    parent_dir = os.path.dirname(os.path.dirname(__file__))
    if parent_dir not in sys.path:
        sys.path.insert(0, parent_dir)
    from base_side_channel_attack import BaseSideChannelAttack, AttackResult
    from cpa_attack import CPAAttack
    from dpa_attack import DPAAttack
    from spa_attack import SPAAttack
    from template_attack import TemplateAttack


@dataclass
class AlgorithmSpecificResult:
    """Algorithm特定AttackResult"""
    algorithm: str
    attack_type: str
    target_operation: str
    success: bool
    confidence: float
    recovered_key_bytes: Optional[List[int]] = None
    top_candidates: Optional[List[Tuple[int, float]]] = None  # [(hypothesis, score), ...]
    execution_time: float = 0.0
    metadata: Dict[str, Any] = None
    
    def __post_init__(self):
        if self.metadata is None:
            self.metadata = {}
        if self.top_candidates is None:
            self.top_candidates = []
    
    def get_rank_of_hypothesis(self, hypothesis: int) -> Optional[int]:
        """
        获取指定False设In候选List中的排名
        
        Args:
            hypothesis: 要Find的False设值
            
        Returns:
            排名（From1Start），IfNotInList中ReturnNone
        """
        for rank, (hyp, score) in enumerate(self.top_candidates, start=1):
            if hyp == hypothesis:
                return rank
        return None
    
    def is_hypothesis_in_top_n(self, hypothesis: int, n: int = 10) -> bool:
        """
        Check指定False设YesNoIn前n个候选中
        
        Args:
            hypothesis: 要Check的False设值
            n: 前n个候选
            
        Returns:
            YesNoIn前n个候选中
        """
        rank = self.get_rank_of_hypothesis(hypothesis)
        return rank is not None and rank <= n


class AlgorithmSpecificAttack(ABC):
    """Algorithm特定Side-Channel Attack基Class"""
    
    def __init__(self, algorithm_name: str):
        """
        InitializeAlgorithm特定Attack
        
        Args:
            algorithm_name: Algorithm名称
        """
        self.algorithm_name = algorithm_name
        self.logger = logging.getLogger(f"AlgorithmAttack.{algorithm_name}")
        
        # Initialize各种AttackMethod
        self.cpa_attack = CPAAttack()
        self.dpa_attack = DPAAttack()
        self.spa_attack = SPAAttack()
        self.template_attack = TemplateAttack()
        
        # Algorithm特定Parameters
        self.key_size = None
        self.block_size = None
        self.round_count = None
        
        # 泄漏模型
        self.leakage_models = {}
        
    @abstractmethod
    def get_algorithm_parameters(self) -> Dict[str, Any]:
        """获取AlgorithmParameters"""
        pass
    
    @abstractmethod
    def get_intermediate_functions(self) -> Dict[str, Any]:
        """获取Intermediate valueCalculateFunction（新架构）"""
        pass
    
    @abstractmethod
    def get_leakage_models(self) -> Dict[str, Any]:
        """获取泄漏表示模型（新架构）"""
        pass
    
    @abstractmethod
    def get_operation_to_intermediate_mapping(self) -> Dict[str, str]:
        """获取目标操作ToIntermediate valueFunction的Mapping（新架构）"""
        pass
    
    @abstractmethod
    def create_leakage_models(self) -> Dict[str, Any]:
        """CreateAlgorithm特定的泄漏模型（组合Intermediate valueAnd泄漏模型）"""
        pass
    
    @abstractmethod
    def get_target_operations(self) -> List[str]:
        """获取可Attack的目标操作"""
        pass
    
    def load_data(self, npz_file: str) -> bool:
        """LoadDataToAllAttackMethod"""
        success = True
        for attack in [self.cpa_attack, self.dpa_attack, self.spa_attack, self.template_attack]:
            if not attack.load_data(npz_file):
                success = False
                self.logger.error(f"Failed to load data for {type(attack).__name__}")
        
        if success:
            self.logger.info(f"Data loaded successfully for {self.algorithm_name} attacks")
        
        return success
    
    def run_cpa_attack(self, target_operation: str = "sbox", **kwargs) -> AlgorithmSpecificResult:
        """
        Run针对特定Algorithm的CPAAttack
        
        Args:
            target_operation: 目标操作
            **kwargs: AttackParameters
            
        Returns:
            Algorithm特定AttackResult
        """
        # SettingsAlgorithm特定的Intermediate valueCalculate
        original_calc = self.cpa_attack.calculate_intermediate_values
        self.cpa_attack.calculate_intermediate_values = lambda td, h: self._calculate_intermediate_values(
            td, h, target_operation
        )
        
        try:
            result = self.cpa_attack.attack(**kwargs)
            
            return AlgorithmSpecificResult(
                algorithm=self.algorithm_name,
                attack_type="CPA",
                target_operation=target_operation,
                success=result.success,
                confidence=result.confidence,
                recovered_key_bytes=[result.best_hypothesis] if result.best_hypothesis is not None else None,
                top_candidates=result.top_candidates,
                execution_time=result.execution_time,
                metadata=result.metadata
            )
        finally:
            # 恢复原始Method
            self.cpa_attack.calculate_intermediate_values = original_calc
    
    def run_dpa_attack(self, target_operation: str = "sbox", **kwargs) -> AlgorithmSpecificResult:
        """Run针对特定Algorithm的DPAAttack"""
        # SettingsAlgorithm特定的选择Function
        original_selection = getattr(self.dpa_attack, '_selection_function', None)
        self.dpa_attack._selection_function = lambda td, h: self._get_selection_function(
            td, h, target_operation
        )
        
        try:
            result = self.dpa_attack.attack(**kwargs)
            
            return AlgorithmSpecificResult(
                algorithm=self.algorithm_name,
                attack_type="DPA",
                target_operation=target_operation,
                success=result.success,
                confidence=result.confidence,
                recovered_key_bytes=[result.best_hypothesis] if result.best_hypothesis is not None else None,
                top_candidates=result.top_candidates,
                execution_time=result.execution_time,
                metadata=result.metadata
            )
        finally:
            # 恢复原始Method
            if original_selection:
                self.dpa_attack._selection_function = original_selection
    
    def run_spa_attack(self, target_operation: str = "key_schedule", **kwargs) -> AlgorithmSpecificResult:
        """Run针对特定Algorithm的SPAAttack"""
        result = self.spa_attack.attack(**kwargs)
        
        return AlgorithmSpecificResult(
            algorithm=self.algorithm_name,
            attack_type="SPA",
            target_operation=target_operation,
            success=result.success,
            confidence=result.confidence,
            top_candidates=result.top_candidates,
            execution_time=result.execution_time,
            metadata=result.metadata
        )
    
    def run_template_attack(self, target_operation: str = "sbox", **kwargs) -> AlgorithmSpecificResult:
        """Run针对特定Algorithm的Template Attack"""
        result = self.template_attack.attack(**kwargs)
        
        return AlgorithmSpecificResult(
            algorithm=self.algorithm_name,
            attack_type="Template",
            target_operation=target_operation,
            success=result.success,
            confidence=result.confidence,
            top_candidates=result.top_candidates,
            execution_time=result.execution_time,
            metadata=result.metadata
        )
    
    def run_all_attacks(self, target_operations: Optional[List[str]] = None, **kwargs) -> Dict[str, AlgorithmSpecificResult]:
        """
        RunAllAttackMethod
        
        Args:
            target_operations: 目标操作List
            **kwargs: AttackParameters
            
        Returns:
            AllAttackResult
        """
        if target_operations is None:
            target_operations = self.get_target_operations()
        
        results = {}
        
        for operation in target_operations:
            self.logger.info(f"Running attacks on {operation} operation")
            
            # CPAAttack
            try:
                cpa_result = self.run_cpa_attack(operation, **kwargs)
                results[f"cpa_{operation}"] = cpa_result
            except Exception as e:
                self.logger.error(f"CPA attack on {operation} failed: {e}")
            
            # DPAAttack
            try:
                dpa_result = self.run_dpa_attack(operation, **kwargs)
                results[f"dpa_{operation}"] = dpa_result
            except Exception as e:
                self.logger.error(f"DPA attack on {operation} failed: {e}")
            
            # SPAAttack（主要针对Key调度）
            if operation in ["key_schedule", "round_key"]:
                try:
                    spa_result = self.run_spa_attack(operation, **kwargs)
                    results[f"spa_{operation}"] = spa_result
                except Exception as e:
                    self.logger.error(f"SPA attack on {operation} failed: {e}")
            
            # Template Attack
            try:
                template_result = self.run_template_attack(operation, **kwargs)
                results[f"template_{operation}"] = template_result
            except Exception as e:
                self.logger.error(f"Template attack on {operation} failed: {e}")
        
        return results
    
    @abstractmethod
    def _calculate_intermediate_values(self, target_data: np.ndarray, hypothesis: int, 
                                     target_operation: str) -> np.ndarray:
        """
        CalculateAlgorithm特定的Intermediate value
        
        Args:
            target_data: 目标Data
            hypothesis: False设值
            target_operation: 目标操作
            
        Returns:
            Intermediate valueArray
        """
        pass
    
    @abstractmethod
    def _get_selection_function(self, target_data: np.ndarray, hypothesis: int,
                              target_operation: str) -> np.ndarray:
        """
        获取Algorithm特定的选择Function
        
        Args:
            target_data: 目标Data
            hypothesis: False设值
            target_operation: 目标操作
            
        Returns:
            选择FunctionResult
        """
        pass
    
    def get_attack_summary(self, results: Dict[str, AlgorithmSpecificResult]) -> Dict[str, Any]:
        """获取AttackResult摘要"""
        successful_attacks = [name for name, result in results.items() if result.success]
        
        summary = {
            "algorithm": self.algorithm_name,
            "total_attacks": len(results),
            "successful_attacks": len(successful_attacks),
            "success_rate": len(successful_attacks) / len(results) if results else 0,
            "best_attacks": {},
            "recovered_key_bytes": set()
        }
        
        # 按AttackClass型分组
        attack_types = {}
        for name, result in results.items():
            attack_type = result.attack_type
            if attack_type not in attack_types:
                attack_types[attack_type] = []
            attack_types[attack_type].append(result)
        
        # 找To每种AttackClass型的最佳Result
        for attack_type, type_results in attack_types.items():
            successful_results = [r for r in type_results if r.success]
            if successful_results:
                best_result = max(successful_results, key=lambda x: x.confidence)
                summary["best_attacks"][attack_type] = {
                    "target_operation": best_result.target_operation,
                    "confidence": best_result.confidence,
                    "recovered_bytes": best_result.recovered_key_bytes
                }
                
                # 收集恢复的Key字节
                if best_result.recovered_key_bytes:
                    summary["recovered_key_bytes"].update(best_result.recovered_key_bytes)
        
        summary["recovered_key_bytes"] = list(summary["recovered_key_bytes"])
        
        return summary
